#include "inc/os.h"

// 1s
#define TIMER_INTERVAL 50000000

/*
 * The TIMER control registers are memory-mapped at address TIMER (defined in inc/platform.h). 
 * This macro returns the address of one of the registers.
 */
#define TIMER_REG_ADDRESS(reg) ((volatile uint32_t *) (TIMER + reg))

/*
 * TIMER registers map
 * timer_count is a read-only reg
 */
#define TIMER_CTRL	0
#define TIMER_COUNT	4
#define TIMER_EVALUE	8

#define timer_read_reg(reg) (*(TIMER_REG_ADDRESS(reg)))
#define timer_write_reg(reg, data) (*(TIMER_REG_ADDRESS(reg)) = (data))

#define TIMER_EN	  1 << 0
#define TIMER_INT_EN      1 << 1
#define TIMER_INT_PENDING 1 << 2

static uint32_t _tick = 0;

#define MAX_TIMER 3
static struct timer timer_list[MAX_TIMER];

void timer_load(uint32_t interval)
{
        timer_write_reg(TIMER_EVALUE, interval);
        timer_write_reg(TIMER_CTRL, (timer_read_reg(TIMER_CTRL) | (TIMER_EN)));
}

/*
 * enable timer interrupt
 */
void timer_init()
{
	timer_write_reg(TIMER_CTRL, (timer_read_reg(TIMER_CTRL) | (TIMER_INT_EN)));
	timer_load(TIMER_INTERVAL);
}

timer *timer_create(void (*handler)(void *arg), void *arg, uint32_t timeout)
{
	/* TBD: params should be checked more, but now we just simplify this */
	if (NULL == handler || 0 == timeout) {
		return NULL;	
	}
	/* use the spin_lock to protect the shared timer_list between multiple tasks */
	spin_lock();
	timer *t = &(timer_list[0]);
	for (int i = 0l; i < MAX_TIMER; i++) {
		if (NULL == t->func) {
			break;
		}
		t++;
	}
	// the timer_list is full
	if (NULL != t->func) {
		spin_unlock();
		return NULL;
	}

	t->func = handler;
	t->arg = arg;
	t->timeout_tick = _tick + timeout;

	spin_unlock();

	return t;
}

void timer_delete(timer *timer)
{
	spin_lock();
	struct timer *t = &(timer_list[0]);
	for (int i = 0; i < MAX_TIMER; i++) {
		if (t == timer) {
			t->func = NULL;
			t->arg = NULL;
			break;
		}
		t++;
	}
	spin_unlock();
}

/* this routine should be called in interrupt context (interrupt is disabled) */
static inline void timer_check()
{
	timer *t = &(timer_list[0]);
	for (int i = 0; i < MAX_TIMER; i++) {
		if (NULL != t->func) {
			if (_tick >= t->timeout_tick) {
				t->func(t->arg);
				/* once time, just delete it after timeout */
				t->func = NULL;
				t->arg = NULL;
				break;
			}
		}
		t++;
	}
}

void timer_handler()
{
	timer_write_reg(TIMER_CTRL, (timer_read_reg(TIMER_CTRL) & ~(TIMER_INT_PENDING)));
	_tick++;
	printf("tick: %d\n", _tick);

	timer_check();
	
	timer_load(TIMER_INTERVAL);

	schedule();
}
